---
title: 'Event recurrence plugin'
description: 'Plugin for creating recurring events according to the RFC5545 specification.'
---

import {Callout} from "nextra/components";

# Event recurrence

## ℹ️ Important note before getting started

This plugin enables you to create recurring events according to the [iCalendar specification](https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.10). Please note, however,
that
this plugin only offers a partial implementation of the specification. There are 2 reasons for this:

1. Some rules just haven't been implemented yet. If there is a rule that you would like to see implemented, please open
an issue on GitHub.
2. Some iCalendar rules are supported for display purposes but cannot be updated via drag & drop. For example, events
using `BYDAY` with `MONTHLY` or `YEARLY` frequencies are fully supported, but cannot be updated by dragging. If you need
to update such events programmatically, you can disable drag & drop for those specific events or handle updates through
the events service.

If you need an almost full implementation of the iCalendar spec, you can use the [rrule](https://www.npmjs.com/package/rrule)
library instead of this plugin. Here's an [example](https://github.com/schedule-x/react-examples/blob/main/rrulejs/src/App.tsx) of how to do so. However, this only works if you're merely using the calendar to display events.
**If you want to use interactive features like drag & drop or resizing, you cannot use the external rrule library**.

## Installation

```sh copy
npm install @schedule-x/event-recurrence
```

## Usage

```js
import { createEventRecurrencePlugin } from "@schedule-x/event-recurrence";

const calendar = createCalendar({
  // ... other config options
  plugins: [
    createEventRecurrencePlugin()
  ],
  events: [
    {
      id: 123,
      title: 'Bi-Weekly Event Monday and Wednesday',
      start: Temporal.ZonedDateTime.from('2024-02-05T14:00:00+01:00[Europe/Berlin]'),
      end: Temporal.ZonedDateTime.from('2024-02-05T15:00:00+01:00[Europe/Berlin]'),
      rrule: 'FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,WE;UNTIL=20240229T235959'
    },
    {
      id: 456,
      title: 'Weekly Event on 4 occasions',
      start: Temporal.PlainDate.from('2024-02-03'),
      end: Temporal.PlainDate.from('2024-02-03'),
      rrule: 'FREQ=WEEKLY;COUNT=4'
    },
    {
      id: 789,
      title: 'Daily event 5 times',
      start: Temporal.ZonedDateTime.from('2024-02-05T12:00:00+01:00[Europe/Berlin]'),
      end: Temporal.ZonedDateTime.from('2024-02-05T13:55:00+01:00[Europe/Berlin]'),
      rrule: 'FREQ=DAILY;COUNT=5',
      calendarId: 'personal',
    },
    {
      id: 121314,
      title: 'Daily event mo-fr 10 times',
      start: Temporal.ZonedDateTime.from('2024-02-05T12:00:00+01:00[Europe/Berlin]'),
      end: Temporal.ZonedDateTime.from('2024-02-05T13:55:00+01:00[Europe/Berlin]'),
      rrule: 'FREQ=DAILY;COUNT=10;BYDAY=MO,TU,WE,TH,FR',
      calendarId: 'work',
    },
    {
      id: 141617,
      title: 'Monthly event on the 7th 5 times',
      start: Temporal.ZonedDateTime.from('2024-02-07T16:00:00+01:00[Europe/Berlin]'),
      end: Temporal.ZonedDateTime.from('2024-02-07T17:55:00+01:00[Europe/Berlin]'),
      rrule: 'FREQ=MONTHLY;COUNT=5',
    },
    {
      id: 151618,
      title: 'Monthly event on the 1st Monday 12 times',
      start: Temporal.ZonedDateTime.from('2024-08-05T04:00:00+01:00[Europe/Berlin]'),
      end: Temporal.ZonedDateTime.from('2024-08-05T06:00:00+01:00[Europe/Berlin]'),
      rrule: 'FREQ=MONTHLY;BYDAY=1MO;COUNT=12',
    },
    {
      rrule: 'FREQ=YEARLY;COUNT=5',
      title: 'Event on the 8th of February for 5 years',
      start: Temporal.ZonedDateTime.from('2024-02-08T16:00:00+01:00[Europe/Berlin]'),
      end: Temporal.ZonedDateTime.from('2024-02-08T17:55:00+01:00[Europe/Berlin]'),
      id: 181920
    }
  ]
});
```

## Supported rules

| Rule                                         | Supported values                                                                                                                                                                     | Required |
|----------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|
| FREQ                                         | `DAILY`, `WEEKLY`, `MONTHLY` or `YEARLY`                                                                                                                                             | Yes      |
| COUNT                                        | Number                                                                                                                                                                               | No       |
| INTERVAL                                     | Number                                                                                                                                                                               | No       |
| BYDAY - compatible with all frequencies      | `MO`, `TU`, `WE`, `TH`, `FR`, `SA`, `SU`. Can optionally be prefixed with a position (e.g., `1MO` for first Monday, `-1FR` for last Friday) but can't be updated using drag & drop.  | No       |
| BYMONTHDAY  - compatible with `MONTHLY`      | (Single) Positive integer 1-31. Does not support list of values, since such lists cannot be updated in a predictable way using drag & drop.                                          | No       |
| UNTIL                                        | Floating [date](https://www.rfc-editor.org/rfc/rfc5545#section-3.3.4), for example `20240101` or [date-time](https://www.rfc-editor.org/rfc/rfc5545#section-3.3.5) `20240101T120000` | No       |
| WKST                                         | `MO`, `TU`, `WE`, `TH`, `FR`, `SA`, `SU`                                                                                                                                             | No       |

## `exdate`

You can exclude certain dates or date-times from a recurrence set using the `exdate` rule. The expected format is RFC5455 floating date (e.g. `20240101`) or datetime (e.g. `20240101T120000`). For example:

```js
const recurringEventWithExclusions = {
  id: 123,
  title: 'Weekly event',
  start: Temporal.ZonedDateTime.from('2024-02-05T14:00:00+01:00[Europe/Berlin]'),
  end: Temporal.ZonedDateTime.from('2024-02-05T15:00:00+01:00[Europe/Berlin]'),

  // will create a recurrence set of 3 events: 2024-02-05 14:00, 2024-02-26 14:00, 2024-03-04 14:00
  rrule: 'FREQ=WEEKLY;COUNT=5',
  exdate: [
    '20240212T140000',
    '20240219T140000'
  ]
}
```

## Events service

When using this plugin, you can no longer use the regular events service plugin. Instead, you
now need to import the events service plugin from this package:

```js
import { createEventRecurrencePlugin, createEventsServicePlugin } from "@schedule-x/event-recurrence";

const recurrencePlugin = createEventRecurrencePlugin();
const eventsServicePlugin = createEventsServicePlugin();

const calendar = createCalendar({
  /* other config */

  plugins: [
    recurrencePlugin,
    eventsServicePlugin
  ]
});

calendar.render(document.getElementById('calendar'));

// Add an event
eventsServicePlugin.add({
  id: 1,
  title: 'New event',
  start: Temporal.ZonedDateTime.from('2024-02-05T14:00:00+01:00[Europe/Berlin]'),
  end: Temporal.ZonedDateTime.from('2024-02-05T15:00:00+01:00[Europe/Berlin]'),
  rrule: 'FREQ=WEEKLY;COUNT=3'
});

// Update
eventsServicePlugin.update({
  id: 1,
  title: 'New event',
  start: Temporal.ZonedDateTime.from('2024-02-05T14:00:00+01:00[Europe/Berlin]'),
  end: Temporal.ZonedDateTime.from('2024-02-05T15:00:00+01:00[Europe/Berlin]'),
  rrule: 'FREQ=WEEKLY;COUNT=10'
})

// Get one event with id 123
const event = eventsServicePlugin.get(123);

// Get all
const events = eventsServicePlugin.getAll();

// Remove an event with id 123
eventsServicePlugin.remove(123);
```


